/**
 *	Yggdrasil World Framework Toolkit
 *	(A component of the NeuroCoSA toolkit)
 *	Copyright (C) 2001-5 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween
 * @license	LGPL
 */

package org.stumeikle.Yggdrasil;

import java.net.*;
import java.io.*;
import java.util.*;


/**
 * world manager thread
 * spawned by the Yggdrasil master thread to manage the world
 * (super user modifyable for different worlds o course)
 *
 * stumeikle 20010403
 * Copyright (C) Stuart Meikle 2001
 *
 *
 * 2006-halloween: making this a little more generic now... world needs to provide
 * adequate interface for body virtual and real worlds. 
 */
public class	World extends Thread
{
    private static int			iMaxNumBodies; /** max number of Bodys in the world*/
    private Vector<Body>		iCreatureWrappers;
    private Version			iTimeStamp; /** Time stamp for data going in/out of the world */
    private long			iWorldTime; /** the world clock */
    
    private FountainOfLife		iXtIncomingCreatures; /** Information about new incoming creatures */
    private BodyFactory			iBodyFactory;/** user installable means to create specific body types */
  	    static Random		rand; /** random number generator */

    /** Constructor. 
     * @param e fountain of life structure, passed in from Yggdrasil.
     * This constructor is typically called once and once only from Yggdrasil class. 
     * Runs in its own thread. 
     */
    public World()
    {
	//set some defaults
	iMaxNumBodies = 100;
	iBodyFactory = null;
    }

    public void setFountainOfLife ( FountainOfLife e)
    {
	iXtIncomingCreatures = e;
    } 

    public void setBodyFactory( BodyFactory b )
    { iBodyFactory = b; }

    public void	setMaxNumBodies( int i )
    { 	iMaxNumBodies = i; 

	//not really the same as the max number of creatures dictated by the server
	//server creatures are creatures which connect via TCPIP
	//also possible to have (AI etc) creatures in the world which are simple and run
	//within the world thread. 
    }

    //called by yggdrasil.
    public void init()
    {
    	FileInputStream		ipfs;
    	ObjectInputStream 	ois;

	// initialise the data structures that we need 
	iCreatureWrappers = new Vector<Body>(iMaxNumBodies);
	iTimeStamp = new Version(1);
	iWorldTime=0;
	rand = new Random();

	//don't load the map - that's specific only to virtual environments. 
	//but here is where the map was loaded previously
	//and where the critters were created
    }
    
    /** Run the world , enter the main eventloop
     */
    public void run()
    {
    	//go! read the creatures actions and update the world!
	System.out.println("WORLD: hello errr world!");

	//this part changed again to be more generic - needs to be suitable for both
	//virtual and real worlds. 
	do
	{
	    //(0) update the clock
	    iWorldTime++;
	    iTimeStamp.setValue( iTimeStamp.getValue()+1 );

	    //(1) check for new connections coming in
	    checkForNewConnections();

	    //(1.5) for each of the creatures...
	    for(int i=0;i<iCreatureWrappers.size();i++)
	    {
	        Body b = (Body) iCreatureWrappers.get(i);

		if (b.getUpdateTime()>iWorldTime)
			continue;

		//else...
		//(2) provide senses to all those elves wot in the world live
	    	//    [FUTURE: this might become providing different senses for
	    	//	   different creatures, or no senses for some. again could
	    	//	   put scheduling in.]
		//(2.1) in the case of real world bodies, collect and process the senses
		b.generateSenses();
		b.exportSenses();
//		yield();

	    	//(3) read in the actions from all the elves/creatures and udpate
	    	//their bodies. this should really trigger state changes
		b.importActions();

	    	//(3.5) update the bodies of the creatures based on their
	    	//on going states. [FUTURE: introduce scheduling here...]
		b.updateBodyPhysics();
		b.setNextUpdateTime(iWorldTime);

		if (b.deadAndBuried())
		{
		    iCreatureWrappers.remove(b);
		    System.out.println("Yggdrasil:World: creature " + b + " died. ");
		}
	    }

	    //(4) write out the body descriptions to file so that the UI can read
	    //them. could use another socket for this really but we used an applet
	    //viewer before
	    //this is not relevant for real worlds. maybe at it back in later. 
	    //writeBodiesToFile();

	    //(5) if there's time update the iBuildings
	    //this is not relevant for real worlds. maybe at it back in later. 
	    //updateInsentients();

	    //could put the GUI in another thread but synchronous painting 
	    //prohibits need to add mutexs
	    updateWorld();
	    try{Thread.sleep(10);}catch(Exception e){}

	}while(true);
    }

/* old code ................
    void	writeBodiesToFile()
    {
        FileOutputStream	fos;
	ObjectOutputStream	oos;

	try
	{
	    fos= new FileOutputStream( "bodies.dat" );
	    oos= new ObjectOutputStream( fos );

	    //write the version first
	    oos.writeObject( iTimeStamp );
	    
//System.out.println("DEBUG: writing " + iCreatureWrappers.size() + " bodies to file");
	    for( int i=0; i<iCreatureWrappers.size(); i++ )
	    {
		//duplicate the body descriptions of each body and
		//write these to file...
		BodyDescription bd = ((Body)iCreatureWrappers.get(i)).getBodyDescription();
		BodyDescription bdc= (BodyDescription) bd.clone();

		if (bdc!=null)
		{
		    oos.writeObject(bdc);
		}
	    }

	    oos.close();
	    fos.close();

	}catch( Exception e )
	{
	    System.out.println("WORLD: unable to write creature posn info to file.");
	}
    }
*/


    /** check for new connections and add to the world. 
     *  also remove bodies from disconnected parties
     */
    void   checkForNewConnections()
    {
	if (iBodyFactory==null)
	    return;

        if ( iXtIncomingCreatures.newCreature() )
	{
	    Random	rand = new Random();

	    //20031103 vary actions with creature type
	    String	t = iXtIncomingCreatures.getNewCreatureType();
	    Life    	l = iXtIncomingCreatures.getLife();
	    Body    	b = null;

	    //create a new creature depending on the type string. 
	    System.out.println("Yggdrasil:World: Client of type " + t + " connecting");
	    b = iBodyFactory.createNewBody(t);
	    if (b!=null)
	    {
	    	b.setLife(l);
	    	iXtIncomingCreatures.acknowledge();
	    	iCreatureWrappers.add(b);
	    } //else close the life propery. FIX
	}
    }
    
    /* no longer used 20031104
    void	updateCreaturePhysics(Body eb)
    */

    /*......... old code 
    void	updateInsentients()
    {
        for(int i=0;i<iInsentients.size(); i++)
	{
	    Insentient b = (Insentient) iInsentients.get(i);

	    if (b.getUpdateTime()>iWorldTime)
			continue;

	    //very clunky for now...
	    InsentientActions	ba = b.getActions();

	    ba.go(iWorldModel);
	    b.setNextUpdateTime( iWorldTime );
	}
    }*/

    public 	void	updateWorld()
    { //OVERLOAD THIS
    }
}

